#!/usr/bin/qdroid
#codepage=Utf-8;


/* "простой chat-сервер" 
    
(v.2009.08.26_0403)  - добавлены команды /NICK /PASS /QUIT
	ЕСТЬ ПРОБЛЕМЫ С УНИЧТОЖЕНИЕМ ОБЪЕКТОВ!
	РАСХОД ПАМЯТИ С РОСТОМ ПОСЕЩЕНИЙ ТОЛЬКО РАСТЕТ!
(v.2009.08.25_2359) 
(v.2009.08.24_0215) - работающая версия. работает: логон, ввод ника, рассылка сообщений с именем автора, отлогон.
(v.2009.07.24_0032) 
(v.2009.07.16_2340) 
порт 18190



*/

// требует версию qDROID не ниже 2009.04.28_1500
var res;

print ("Инициация расширений.\n");
print ("Код результата загрузки модулей (-1=error; 0=ok; +1=warnings):\n\n");

res=loadlib_qd("qdlib_tcpserver");
print ("[qdlib_tcpserver] = "+res);

res=loadlib_qd("seriallink_otpd");
print ("[qdlib_tcpserver] = "+res);

res=loadlib_qd("qdlib_bytearray");
print ("[qdlib_tcpserver] = "+res);

//==================================================================
var MsgText
var HeadText;

//параметры TCP-сервера.
var SrvAddress = "127.0.0.1";
var SrvPort =18190;

var SystemUserID = 0;// ID  пользователя чата оссоциированного с системным.
var count=1; //счетчик порядкового номера числа использованных слотов для клиентов
var countConn=1;//счетчик посещений

//прочие настройки

//глобальные классы
//var ClientConnection;

var ObjTcpServer;
var ConnArray_UserObjects = new Array(); //объекты t_ChatUser
//var ConnArray_Used = new Array(); //признак используемости слота

//---------инициируем tcp-сервер--------
ObjTcpServer=new TcpServer;
print ("(*) создан ["+ObjTcpServer.GetId()+"]");
res=ObjTcpServer.listen(SrvAddress, SrvPort );
print ("[] TcpServer:: start result: ["+res+"]");


//-------------------------------------------------
// инициируем системного пользователя.
ConnArray_UserObjects[SystemUserID] =new t_ChatUser(new TcpSocket(), 0,"SystemDroddy");

//-------------------------------------------------
//подключим сигнал о появлении нового соединения.
// ну, или "обработчик события" "новое соединение" .
ObjTcpServer.newConnection.connect(newConnectionSLOT);
print ("[[2]] TcpServer started.");

print ("Welcome! ver 2009.08.22_2344 .\n");
//

//==================================================================
/*
надо создавать объект на скрипте, который будет агрегировать соединение 
и нести некоторые функции связанные с его свойствами - имя, функциональность и т.п.

 в версии 0 это был "броадкастер"... он просто пересылал "от одного всем".
в версии 1 надо создать объект.



*/



//==================================================================
//slot function for new connection appeared.
function newConnectionSLOT()
  {
	print ("New Connection appeared!");
	// Собственно это пока все, т.к. класс qdl_TcpSocket пока не реализован.
	
	print ("[+] Connected new user .No["+countConn+"]");
	countConn++;
	
	//ClientConnection = ObjTcpServer.nextPendingConnection_qd();
	
	var SlotToUse=count;
	for (w in ConnArray_UserObjects)
	   {
		if (w==SystemUserID)  continue; //cистема и без этого все знает
		if (ConnArray_UserObjects[w].online==false)
		   { 
			if (ConnArray_UserObjects[w].clientConnection.state() != 0) continue;
			SlotToUse=w;
		   };
		
	   };
	print ("[+] Slot ["+SlotToUse+"] from ["+count+"]");
	//ВНИМАНИЕ! тут "багофича"! в массив попадает копия объекта-обертки а не оригинал!
	//ConnArray_UserObjects[count] = new t_ChatUser( ClientConnection, count, "Droddy_"+count );
	ConnArray_UserObjects[SlotToUse] = new t_ChatUser( ObjTcpServer.nextPendingConnection_qd(), count, "Droddy_"+count );
	ConnArray_UserObjects[SlotToUse].clientConnection.write( (new ByteArray()).append("Welcome to Droddy-chat, simple tcp-chat server\n DroddyIRS, wrote with QDroid-framework.\n Use commands /PASS and /NICK to enter chat : \n >>") );
		
	ConnArray_UserObjects[SlotToUse].clientConnection.disconnected.connect(closeClientConnection);
	ConnArray_UserObjects[SlotToUse].clientConnection.readyRead.connect(resend_tcp2all_readyRead);
		//надо-бы ещё вход подключения замкнуть на рассылку всем. .... но потом...
		
	if (SlotToUse==count) count++;
		
	//ClientConnection=null;
		
	//НАДО ПОЗАБОТИТЬСЯ О ТОМ ЧТО БЫ ОБЪЕКТ УНИЧТОЖАЛСЯ...
	// ХОТЯ МОЖНО БЫ И САМОМУ УДАЛИТЬ...
		
	return "ok";
  };

//==================================================================
//сервисная функция для отображения данных ByteAraay
function displayData(_data,direction)
{
	var p;
	for (p in _data) 
	   {
		print ("["+p+"]"+direction+"[" + _data[p] + "]");
	   };
};

//------------------------------------------------------------------
//закроем текущее соединение и установим статус по умолчанию
function closeClientConnection()
  {

	for (w in ConnArray_UserObjects)
	  if (ConnArray_UserObjects[w].online)
	    {
		if (w==SystemUserID)  continue; //cистема и без этого все знает
		if (ConnArray_UserObjects[w].clientConnection.state() ==3) continue;
		ConnArray_UserObjects[w].online=false;
		//надо отключиться от всех сигналов к нам подключенных
		// пока руками... 
		// TODO :: надо бы придумать как одним махом отключаться от всего что к нас подключено...
		// и включить это в общий прототип 
		ConnArray_UserObjects[w].clientConnection.disconnected.disconnect( closeClientConnection );
		ConnArray_UserObjects[w].clientConnection.readyRead.disconnect( resend_tcp2all_readyRead );

		send_toall_bytearray(SystemUserID, (new ByteArray).append(" < "+ConnArray_UserObjects[w].name + " out >" ));
		ConnArray_UserObjects[w].clientConnection.deleteLater();
	   };
	
	
	//ClientConnection.deleteLater();
	print ("Connection closed.");	
//	return 0;
   };

//==================================================================
//------------------------------------------------------------------
// как только на сокете появляются данные - вываливаем их все в компорт
// жалко прямого коннекта не получится...
function resend_tcp2all_readyRead()
  {
	//сюда попадаем при любом посыле данных
	// но мы не можем знать кто именно.	 :((
	// произведем перебор всех, и разошлем все от всех (всем кроме автора)
	var w;

	for (w in ConnArray_UserObjects)
	    {
		if (ConnArray_UserObjects[w].online)
		  if (ConnArray_UserObjects[w].clientConnection.bytesAvailable()>0)
		   {
			ConnArray_UserObjects[w].processData();
			//_data = ConnArray_UserObjects[w].clientConnection.readAll();
			//send_toall(w,_data);// надо бы потом убрать перевод строки в конце...
		  };//if bytesAvailable()>0
	    };
	return 0;  
  };
//------------------------------------------------------------------
// разошлем всем окромя автора и системного
function send_toall(fromWho_ID,_whatToSend_str)
  {
	send_toall_bytearray(fromWho_ID,(new ByteArray).append(_whatToSend_str));
  }

function send_toall_bytearray(fromWho_ID,_whatToSend_barr)
  {
	var _dataMsg = new ByteArray;
	var _data = new ByteArray;
	var q;
	
	_dataMsg.append("<"+ConnArray_UserObjects[fromWho_ID].name+"> ");
	_data=_whatToSend_barr.append("\n");
	
	for (q in ConnArray_UserObjects)
	  {
		if (ConnArray_UserObjects[q].online==false)  continue; // отключенным посылать нет смысла
		if (q==SystemUserID)  continue; //cистема и без этого все знает
		if (q==fromWho_ID) continue; //сами себе не посылаем
		//_dataMsg.chop(_dataMsg.size());//untill we have no clear() function ))))
		ConnArray_UserObjects[q].clientConnection.write(_dataMsg);// <<---------- ошибка сегментирования?! при попытке преобразовать строку в ByteArray?
		ConnArray_UserObjects[q].clientConnection.write(_data);
	  };

 }

//------------------------------------------------------------------
// как только на сокете появляются данные - вываливаем их все в компорт
// жалко прямого коннекта не получится...
/*
function logon_readyRead()
  {
	
  };
/* */

//------------------------------------------------------------------
// проверка того, используется ли этот ник уже или нет? 
function nickIsInUse(_NickName_str)
  {
	return false; //пока всегде не используется.
  };

//==================================================================
/*                    Класс                                                                                         */
//==================================================================

//function t_ChatUset(_ClientConnection)
function t_ChatUser(_ClientConnection, _ID, _Name)
{
    this.clientConnection = _ClientConnection;
    //this.clientConnection.readyRead.connect( this.inData);// к себе-же и подключим
    this.name = _Name;
    this.ip = '';
    this.online = true;
    this.id=_ID;
    this.mode="login in";

    this.currentString="";
    this.recodeMode="";//название кодировки клиента !!!! НЕ ПОДДЕРДИВАЕТСЯ ПОКА !!!!

    this.irc_password="";//password resived from client
    this.irc_nickname="";//

}
 // set the prototype to be an instance of the base class
 // t_ChatUser.prototype = new baseCasss();

 t_ChatUser.prototype.toString = function() { return "[" + this.name + "]"; }
/* 
 t_ChatUser.prototype.inData = function() 
  { 
	var _data= new ByteArray;
	print (""+this.name+" ");
	//_data=this.clientConnection.readAll();
	print ("            : ["+_data.toLatin1String()+"]"); 
  };
/* */
/*    TODO: доработать так, что бы только по пришествию "ентера" 
	строка считалась как отосланная.
	ввести поддержку команд!
*/
t_ChatUser.prototype.processData = function()
  {
	var _data = this.clientConnection.readAll();
	for (p in _data) 
	  {
		this.processIncByte(_data[p]);
		//print ("["+p+"]"+"[" + _data[p] + "]");
	  };
	print (this.currentString);
  };
//--------------------------------------------
// обработать поступившие символы.
// в том числе учитывая возможность поступления команды в нескольких пакетах или побайтно
t_ChatUser.prototype.processIncByte  = function(_byte) 
{
	var code_E1=13;
	var code_E2=10;
	//print ("<<"+_byte+">>");
	if ((_byte==code_E1) | (_byte==code_E2))
	  {
		this.processString(this.currentString);
		this.currentString="";
		return 0;
	  };
	this.currentString=this.currentString+String.fromCharCode(_byte);//пока не будем заморачиваться с кодировками.
	//print ("tmpstr= ["+this.currentString+"]");

	return 0;
}
//--------------------------------------------
// обработать строку (которая может быть и не командой)
t_ChatUser.prototype.processString =  function(_String_str)
{
	var Comand = new String(_String_str);
	//BBot.str_trim(_comString)) - с этим есть какая-то проблема.
	
	if (Comand.length >0 )
	  {
		if (Comand.charAt(0)=="/") // yahhooo! this is command!
		  {
			Comand=Comand.substring(1, Comand.length);
			Parametrs=Comand.split(" ", 1); //получили массив элементов разделенных в оригинале пробелом но неболее 10. Остальная часть строки - теряется.
			Comand_0=Parametrs.shift();
			Parametrs=Comand.substring(Comand_0.length+1, Comand.length);//передадим туда не массив а строку.. разобрать строку в массив легко, обратно сложнее.
			print ("resived Comand <"+this.name+"> ["+Comand_0+"]");
			this.processCommand(Comand_0, Parametrs);
			return 0;
		  }
		else
		  {
			//print ("resive string ["+BBot.str_trim(Comand)+"]"+Comand.length+";");
			this.processMessage(Comand);
			return 0;
		  };
	  }
	else 
	 {	//пустая строка. ничего не делаем.
		//print ("empty command. ");
	 };

}
//--------------------------------------------
// обработать команду (именно уже команду - которая начинается на "слеш")
t_ChatUser.prototype.processCommand =  function(_comString_str, _argArray_str)
{
	var Comand = new String(_comString_str);
	var Arguments_str = new String(_argArray_str);
	var Arguments = (new String(_argArray_str)).split(" ", 10);
	// print (":Comand:"+Comand);
	// print (":0:"+Arguments_str);
	// print (":1:"+_argArray[1]);
	// print (":2:"+_argArray[2]);
	
	if (Comand =="PASS") // команда IRC указания пароля. должна быть дана ПЕРЕД NAME
	  { //устанавливаем 
		// ERR_NEEDMOREPARAMS              ERR_ALREADYREGISTRED
		this.irc_password=Arguments[0]; //первый элемент - пароль
		this.clientConnection.write((new ByteArray).append("<DRODDYIRC> Password accept\n>>" ));
		return 0;
	  };
	if (Comand =="NICK") // команда IRC изменения имени
	  {
		// ERR_NONICKNAMEGIVEN             ERR_ERRONEUSNICKNAME
		// ERR_NICKNAMEINUSE               ERR_NICKCOLLISION
		// ERR_UNAVAILRESOURCE             ERR_RESTRICTED
		if (this.irc_password=="")
		  {// пароль не задан. пока ничего не делаем
		  };
		if (nickIsInUse(Arguments[0])==true)
		  {// не можем законнектить потому что ник уже запользован (зарегистрированные ники не рассматриваем)
			this.clientConnection.write((new ByteArray).append("ERR_NICKNAMEINUSE\n>>" ));
		  }
		else
		  {// а почему-бы не принять ?
			this.name=Arguments[0];
			if (this.irc_nickname=="")
			  {
				this.clientConnection.write((new ByteArray).append("<DRODDYIRC> NickName accepted. Now you are logged in. Welcome!\n>>"));
				send_toall_bytearray(SystemUserID, (new ByteArray).append("user  ["+this.name+"] logged in."));
				print ("New User logged in: ["+this.name+"]");
				this.irc_nickname=this.name;
				this.mode="chating";
			  }
			else
			  {
				this.clientConnection.write((new ByteArray).append("<DRODDYIRC> new NickName accepted.\n>>"));
				send_toall_bytearray(SystemUserID, (new ByteArray).append("user  ["+this.irc_nickname+"] changed name to ["+this.name+"]."));
				this.irc_nickname=this.name;
				this.mode="chating";
			  };
		  };
		return 0;
	  }// Comand =="NAME"
	if (Comand =="QUIT") // команда IRC выхода
	  { //устанавливаем 
		// 
		this.irc_password=Arguments[0]; //первый элемент - пароль
		this.clientConnection.write((new ByteArray).append("<DRODDYIRC> Bye!\n>>" ));
		send_toall_bytearray(SystemUserID, (new ByteArray).append("user  ["+this.irc_nickname+"] has quit DroddyIRC "+Arguments_str+"."));
		
		this.clientConnection.disconnectFromHost();
		//closeClientConnection();
		//this.clientConnection.deleteLater();

		return 0;
	  };
	this.clientConnection.write((new ByteArray).append("<DRODDYIRC> Comman not understood! \n>>" ));

};

//--------------------------------------------
// обработать сообщение (сообщение всем)
t_ChatUser.prototype.processMessage = function(_Message_str)
{
	var Message=String(_Message_str);
	if (this.mode=="chating")
	  {
		send_toall_bytearray(this.id, (new ByteArray).append(Message));// надо бы потом убрать перевод строки в конце...
	  }
	 else
	 {
		this.clientConnection.write((new ByteArray).append("<DRODDYIRC> ERR_NOTLOGGEDIN\n>>" ));
	 };
};

//==================================================================
/*                                                                                                        */
//==================================================================
print ("GO!\n");
